library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.2.1     ✓ purrr   0.3.3
✓ tibble  2.1.3     ✓ dplyr   0.8.3
✓ tidyr   1.0.0     ✓ stringr 1.4.0
✓ readr   1.3.1     ✓ forcats 0.4.0
── Conflicts ────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(lubridate)

Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date

Here is our first task:


Part 1: Assemble the project cohort

The project goal is to identify patients seen for drug overdose, determine if they had an active opioid at the start of the encounter, and if they had any readmissions for drug overdose.

Your task is to assemble the study cohort by identifying encounters that meet the following criteria:

  1. The patient’s visit is an encounter for drug overdose
  2. The hospital encounter occurs after July 15, 1999
  3. The patient’s age at time of encounter is between 18 and 35 (Patient is considered to be 35 until turning 36)

Sounds great. Let’s start by taking a look at the data.

allergies <- read_csv("datasets/allergies.csv")
Parsed with column specification:
cols(
  START = col_date(format = ""),
  STOP = col_date(format = ""),
  PATIENT = col_character(),
  ENCOUNTER = col_character(),
  CODE = col_double(),
  DESCRIPTION = col_character()
)
allergies

encounters <- read_csv("datasets/encounters.csv")
Parsed with column specification:
cols(
  Id = col_character(),
  START = col_datetime(format = ""),
  STOP = col_datetime(format = ""),
  PATIENT = col_character(),
  PROVIDER = col_character(),
  ENCOUNTERCLASS = col_character(),
  CODE = col_double(),
  DESCRIPTION = col_character(),
  COST = col_double(),
  REASONCODE = col_double(),
  REASONDESCRIPTION = col_character()
)
encounters

medications <- read_csv("datasets/medications.csv")
Parsed with column specification:
cols(
  START = col_date(format = ""),
  STOP = col_date(format = ""),
  PATIENT = col_character(),
  ENCOUNTER = col_character(),
  CODE = col_double(),
  DESCRIPTION = col_character(),
  COST = col_double(),
  DISPENSES = col_double(),
  TOTALCOST = col_double(),
  REASONCODE = col_double(),
  REASONDESCRIPTION = col_character()
)
medications

patients <- read_csv("datasets/patients.csv")
Parsed with column specification:
cols(
  .default = col_character(),
  BIRTHDATE = col_date(format = ""),
  DEATHDATE = col_date(format = ""),
  ZIP = col_double()
)
See spec(...) for full column specifications.
patients

procedures <- read_csv("datasets/procedures.csv")
Parsed with column specification:
cols(
  DATE = col_date(format = ""),
  PATIENT.x = col_character(),
  ENCOUNTER = col_character(),
  CODE.x = col_character(),
  DESCRIPTION.x = col_character(),
  COST.x = col_double(),
  REASONCODE.x = col_double(),
  REASONDESCRIPTION.x = col_character()
)
procedures

Ok, we are chiefly interested in the encounters table, and basically want to filter it based on the specifications given in the task. Let’s start by filtering the encounters by drug overdose. Looking at the data dictionary sheet for the encounters table, we can see that the REASONCODE column are SNOMED-CT codes.

We can lookup the code for a drug overdose here: https://browser.ihtsdotools.org/, which has the code as 55680006.

drug_overdoses <- filter(encounters, REASONCODE == 55680006)
drug_overdoses

Great, now we just need to filter for encounters that occur after July 15, 1999.

The encounters table has two column that represent the date of the encounter. START and STOP, further clarification would be neccessary to determine if the task is to find encounters that begin after 07/15/1999 or end at that date. For the purposes of this exercise, we’ll go with encounters that begin after that date due to the term occur in the specification.

after_date <- filter(drug_overdoses, START > "1999-07-15")
arrange(after_date, START)

Now we’re concerned with encounters with patients between the ages of 18 and 35; we’ll need to join the patients table to handle that.

with_patients <- inner_join(after_date, patients, c("PATIENT" = "Id"))
with_patients

Based upon the wording in the specifications, the patient’s age must be greater than or equal to 18 at the start of an encounter and less than or equal to 35 at the end of the encounter.

Let’s make sure that there are no encounters in our table that has not ended, because a patient could age to 36 by the time the encounter is over.

not_ended <- drop_na(with_patients, STOP)
not_ended

Turns out we’re ok. Let’s do the filtering now. First we’ll need to calculate the age of the patient at the start and end of the encounter.

age <- mutate(not_ended, AGEATSTART = as.period(interval(BIRTHDATE, START))$year)
age <- mutate(age, AGEATSTOP = as.period(interval(BIRTHDATE, STOP))$year)
select(age, Id, AGEATSTART, AGEATSTOP)
aged <- filter(age, AGEATSTART >= 18 & AGEATSTOP <= 35)
aged

That finishes up the first task.


Part 2: Create additional fields

With your drug overdose encounter, create the following indicators:

  1. DEATH_AT_VISIT_IND: 1 if patient died during the drug overdose encounter, 0 if the patient died at a different time
  2. COUNT_CURRENT_MEDS: Count of active medications at the start of the drug overdose encounter
  3. CURRENT_OPIOID_IND: 1 if the patient had at least one active medication at the start of the overdose encounter that is on the Opioids List (provided below), 0 if not
  4. READMISSION_90_DAY_IND: 1 if the visit resulted in a subsequent drug overdose readmission within 90 days, 0 if not
  5. READMISSION_30_DAY_IND: 1 if the visit resulted in a subsequent drug overdose readmission within 30 days, 0 if not overdose encounter, 0 if not
  6. FIRST_READMISSION_DATE: The date of the index visit’s first readmission for drug overdose. Field should be left as N/A if no readmission for drug overdose within 90 days

Opioids List: * Hydromorphone 325Mg * Fentanyl – 100 MCG * Oxycodone-acetaminophen 100 Ml


Ok, looking at the data, it seems the only field we have to infer death on is in the patients table with the DEATHDATE column. If the date falls within the encounter dates, then we’ll mark it 1. The specifications don’t state what to do if the patient hasn’t died, this would need clarification, but for the purposes of this exercise we’ll leave it blank in those cases.

died <- mutate(aged, DEATH_AT_VISIT_IND = as.integer(DEATHDATE >= START & DEATHDATE <= STOP))
select(died, START, STOP, DEATHDATE, DEATH_AT_VISIT_IND)

For COUNT_CURRENT_MEDS we’ll have to used the medications table.

For CURRENT_OPOID_IND we’ll have to lookup the codes for the opoids in question; however, the codes in the medications table do not appear to match up with results found on: https://mor.nlm.nih.gov/RxNav/ (which is the RxNorm database the data dictionary mentioned). We would need clarification on this, but for this exercise we’ll search by the DESCRIPTION column instead.

Some drugs have multiple components/ingredients. It’s unsure whether we only should concern ourselves with the pure drugs of interest or also these. For example: Amlodipine 5 MG / Fentanyl 100 MCG / Olmesartan medoxomil 20 MG vs Fentanyl 100 MCG. We would need clarification, but for the exercise we’ll only examine the pure drugs because multiple ingredients can modulate the effects of the drug in question, this is of course an assumption; and it’s a little closer to the specification.

opoids = c("Hydromorphone 325 MG", "Fentanyl 100 MCG", "Oxycodone-acetaminophen 100ML")

get_meds <- function(start, pt) {
  filter(medications, PATIENT == pt & START <= start & STOP <= start)
}

current_meds <- died %>%
  mutate(CURRENTMEDS = pmap(list(START, PATIENT), get_meds)) %>%
  mutate(COUNT_CURRENT_MEDS = map_int(CURRENTMEDS, nrow)) %>%
  mutate(CURRENT_OPOID_IND = map_int(CURRENTMEDS, function(med) any(med$DESCRIPTION %in% opoids)))
         
select(current_meds, CURRENTMEDS, COUNT_CURRENT_MEDS, CURRENT_OPOID_IND)

Now we can begin looking at readmissions. Let’s calculate how many days it will be to the next encounter.

readmission <- current_meds %>%
  arrange(START) %>%
  group_by(PATIENT) %>%
  mutate(READMISSION = as.period(lead(START) - STOP)$day) %>%
  mutate(FIRST_READMISSION_DATE = lead(START)) %>%
  ungroup

readmission$READMISSION
  [1]   NA   NA 5476 2213   NA   NA 3634   NA   NA   NA   NA   NA   NA 5047 5023   NA 3807   NA   NA   NA   NA
 [22] 3069   NA 3694   NA  183   NA 2783 3966  739   NA   NA 2691   NA   NA   NA  278 2669  547   NA 3481   NA
 [43] 3781 2098   NA   NA   NA 1680 3767   NA 2414   NA 2431 2429  938 1922   NA   NA   NA 2435 1681 2592 2919
 [64]   NA 1239 4060 1437   NA 1194 1440 1156   NA   NA   NA 2653 1430 2453  927   NA 2872  186 1268   NA  854
 [85] 2262   NA 1023   NA  290 1410 1939   NA  789   NA  308  524 1082   NA   19  640   NA   NA 1229  349 1040
[106]  244   93  113 2600  415 1061  403  626  531  465 1342 1455 2238 1441  118   NA   NA  309  909 2264  244
[127]  636 1164 2492   NA   89   56   NA  180  749   NA   NA   79 1691   NA  231  624 1472   NA  305  640  577
[148]   NA   NA   NA  100  735   NA  419   NA  514   NA 2300   NA 1440  232   NA  727  222  744   NA  999  219
[169] 1283  917  877   NA  360   NA   NA  734 1646  314  631   NA  421  437  506  522   NA 1663  475   NA   NA
[190]   NA  424  259   60   NA  418 1671 2406  383  852 1638 1363 1713   NA   NA   NA  580  496  476   70  295
[211]  768   NA  761 2328 1157 1146  206   NA   NA  255 1603  130   NA 1426  308  665   NA   NA  165   NA  417
[232]  268  768  413 1078   NA  482   NA   NA  364   NA  261   NA  435  338   NA 2064  752  120 1703   NA   NA
[253]  762  548  473   NA  363   NA 1143   NA   NA   NA   NA 1542   NA  413  920 1176   NA  312 1246   NA  300
[274] 1284   NA  996   NA   NA  305 1329   NA   NA  127  497 1084  257 1597  947 1304 1421  418   NA 1317   NA
[295]   82 1411  331   NA   NA   24 1141 1126   NA  561   NA 1301  431 1401   NA   NA   NA   NA 1328  166   NA
[316]  351   NA  250   NA   NA   NA   NA  342  387   NA   NA  130   94   NA  664   NA   NA   NA   NA  501  698
[337]   NA   20   NA  103   NA  381   NA  380  289   NA   NA  261   NA   83   NA   25   NA   NA   NA   NA   NA
[358]   NA   NA   NA  370   NA   NA   NA  308   NA  133   NA   NA  244   NA  212   NA   NA   NA   NA  335   NA
[379]   NA   NA   NA  256   NA   NA  232   74   NA   NA   NA   NA   93   NA   NA  186   NA  152   NA   NA  307
[400]   NA   NA   62   NA   NA   NA   NA   62   NA   NA   NA  151   NA   NA   43   NA   34   NA   NA   NA   NA
[421]   NA   NA   NA   NA   NA   NA   NA   NA   NA   NA   NA

Great, now we can add the READMISSION_90_DAY_IND and READMISSION_30_DAY_IND columns.

filtered_readmission <- readmission %>%
  mutate(READMISSION_90_DAY_IND = as.integer(READMISSION <= 90)) %>%
  mutate(READMISSION_30_DAY_IND = as.integer(READMISSION <= 30))

arrange(select(filtered_readmission, READMISSION, READMISSION_90_DAY_IND, READMISSION_30_DAY_IND), READMISSION)

Earlier when figuring out how many days there was till a readmission, we also put in what the readmission date is. We can use that here. This works because before doing that, we sorted the group dataframes by START. This way the next date is guaranteed to be the first readmission after a specific encounter.

#first_readmission_dated <- mutate(filtered_readmission, FIRST_READMISSION_DATE = as_datetime(ifelse(READMISSION_90_DAY_IND == 1, READMISSIONDATE, NA)))
first_readmission_dated <- mutate(filtered_readmission, FIRST_READMISSION_DATE = replace(FIRST_READMISSION_DATE, READMISSION_90_DAY_IND == 0, NA))

arrange(select(first_readmission_dated, READMISSION, READMISSION_90_DAY_IND, READMISSION_30_DAY_IND, FIRST_READMISSION_DATE), READMISSION)

And now we can move onto the final task.


Part 3: Export the data to a CSV file

Export a dataset containing these required fields:

Field name Field Description Data Type
PATIENT_ID Patient identifier Character String
ENCOUNTER_ID Visit identifier Character string
HOSPITAL_ENCOUNTER_DATE Beginning of hospital encounter date Date/time
AGE_AT_VISIT Patient age at admission Num
DEATH_AT_VISIT_IND Indicator if the patient died during the drug overdose encounter. Leave N/A if patient has not died, 0 /1
COUNT_CURRENT_MEDS Count of active medications at the start of the drug overdose encounter Num
CURRENT_OPIOID_IND if the patient had at least one active medication at the start of the overdose encounter that is on the Opioids List (provided below) 0/1
READMISSION_90_DAY_IND Indicator if the visit resulted in a subsequent readmission within 90 days 0/1
READMISSION_30_DAY_IND Indicator if the visit resulted in a subsequent readmission within 30 days 0/1
FIRST_READMISSION_DATE Date of the first readmission for drug overdose within 90 days. Leave N/A if no readmissions for drug overdose within 90 days. Date/time

This answers our question about DEATH_AT_VISIT_IND earlier. Our assumption was correct to leave it as N/A if the patient has not died.

Now we just need to grab these fields and write it to a CSV.

output <- first_readmission_dated %>%
  select(PATIENT, Id, START, AGEATSTART, DEATH_AT_VISIT_IND, COUNT_CURRENT_MEDS, CURRENT_OPOID_IND, READMISSION_90_DAY_IND, READMISSION_30_DAY_IND, FIRST_READMISSION_DATE) %>%
  rename(PATIENT_ID = PATIENT, ENCOUNTER_ID = Id, HOSPITAL_ENCOUNTER_DATE = START, AGE_AT_VISIT = AGEATSTART)
  
output
readr::write_csv(output, "submission.csv", na = "N/A", append = FALSE)
LS0tCnRpdGxlOiAiQ0hRQSBBbmFseXN0IHRha2UtaG9tZSB0YXNrIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobHVicmlkYXRlKQpgYGAKCkhlcmUgaXMgb3VyIGZpcnN0IHRhc2s6CgotLS0KCiMjIFBhcnQgMTogQXNzZW1ibGUgdGhlIHByb2plY3QgY29ob3J0ClRoZSBwcm9qZWN0IGdvYWwgaXMgdG8gaWRlbnRpZnkgcGF0aWVudHMgc2VlbiBmb3IgZHJ1ZyBvdmVyZG9zZSwgZGV0ZXJtaW5lIGlmIHRoZXkgaGFkIGFuIGFjdGl2ZSBvcGlvaWQgYXQgdGhlIHN0YXJ0IG9mIHRoZSBlbmNvdW50ZXIsIGFuZCBpZiB0aGV5IGhhZCBhbnkgcmVhZG1pc3Npb25zIGZvciBkcnVnIG92ZXJkb3NlLgoKWW91ciB0YXNrIGlzIHRvIGFzc2VtYmxlIHRoZSBzdHVkeSBjb2hvcnQgYnkgaWRlbnRpZnlpbmcgZW5jb3VudGVycyB0aGF0IG1lZXQgdGhlIGZvbGxvd2luZyBjcml0ZXJpYToKCjEuIFRoZSBwYXRpZW504oCZcyB2aXNpdCBpcyBhbiBlbmNvdW50ZXIgZm9yIGRydWcgb3ZlcmRvc2UKMi4gVGhlIGhvc3BpdGFsIGVuY291bnRlciBvY2N1cnMgYWZ0ZXIgSnVseSAxNSwgMTk5OQozLiBUaGUgcGF0aWVudOKAmXMgYWdlIGF0IHRpbWUgb2YgZW5jb3VudGVyIGlzIGJldHdlZW4gMTggYW5kIDM1IChQYXRpZW50IGlzIGNvbnNpZGVyZWQgdG8gYmUgMzUgdW50aWwgdHVybmluZyAzNikKCi0tLQoKU291bmRzIGdyZWF0LiBMZXQncyBzdGFydCBieSB0YWtpbmcgYSBsb29rIGF0IHRoZSBkYXRhLgoKYGBge3J9CmFsbGVyZ2llcyA8LSByZWFkX2NzdigiZGF0YXNldHMvYWxsZXJnaWVzLmNzdiIpCmFsbGVyZ2llcwoKZW5jb3VudGVycyA8LSByZWFkX2NzdigiZGF0YXNldHMvZW5jb3VudGVycy5jc3YiKQplbmNvdW50ZXJzCgptZWRpY2F0aW9ucyA8LSByZWFkX2NzdigiZGF0YXNldHMvbWVkaWNhdGlvbnMuY3N2IikKbWVkaWNhdGlvbnMKCnBhdGllbnRzIDwtIHJlYWRfY3N2KCJkYXRhc2V0cy9wYXRpZW50cy5jc3YiKQpwYXRpZW50cwoKcHJvY2VkdXJlcyA8LSByZWFkX2NzdigiZGF0YXNldHMvcHJvY2VkdXJlcy5jc3YiKQpwcm9jZWR1cmVzCmBgYAoKT2ssIHdlIGFyZSBjaGllZmx5IGludGVyZXN0ZWQgaW4gdGhlIGBlbmNvdW50ZXJzYCB0YWJsZSwgYW5kIGJhc2ljYWxseSB3YW50IHRvIGZpbHRlciBpdCBiYXNlZCBvbiB0aGUgc3BlY2lmaWNhdGlvbnMgZ2l2ZW4gaW4gdGhlIHRhc2suIExldCdzIHN0YXJ0IGJ5IGZpbHRlcmluZyB0aGUgZW5jb3VudGVycyBieSBkcnVnIG92ZXJkb3NlLiBMb29raW5nIGF0IHRoZSBkYXRhIGRpY3Rpb25hcnkgc2hlZXQgZm9yIHRoZSBgZW5jb3VudGVyc2AgdGFibGUsIHdlIGNhbiBzZWUgdGhhdCB0aGUgYFJFQVNPTkNPREVgIGNvbHVtbiBhcmUgU05PTUVELUNUIGNvZGVzLgoKV2UgY2FuIGxvb2t1cCB0aGUgY29kZSBmb3IgYSBkcnVnIG92ZXJkb3NlIGhlcmU6IGh0dHBzOi8vYnJvd3Nlci5paHRzZG90b29scy5vcmcvLCB3aGljaCBoYXMgdGhlIGNvZGUgYXMgYDU1NjgwMDA2YC4KCmBgYHtyfQpkcnVnX292ZXJkb3NlcyA8LSBmaWx0ZXIoZW5jb3VudGVycywgUkVBU09OQ09ERSA9PSA1NTY4MDAwNikKZHJ1Z19vdmVyZG9zZXMKYGBgCgpHcmVhdCwgbm93IHdlIGp1c3QgbmVlZCB0byBmaWx0ZXIgZm9yIGVuY291bnRlcnMgdGhhdCBvY2N1ciBhZnRlciBKdWx5IDE1LCAxOTk5LgoKVGhlIGBlbmNvdW50ZXJzYCB0YWJsZSBoYXMgdHdvIGNvbHVtbiB0aGF0IHJlcHJlc2VudCB0aGUgZGF0ZSBvZiB0aGUgZW5jb3VudGVyLiBgU1RBUlRgIGFuZCBgU1RPUGAsIGZ1cnRoZXIgY2xhcmlmaWNhdGlvbiB3b3VsZCBiZSBuZWNjZXNzYXJ5IHRvIGRldGVybWluZSBpZiB0aGUgdGFzayBpcyB0byBmaW5kIGVuY291bnRlcnMgdGhhdCBfYmVnaW5fIGFmdGVyIDA3LzE1LzE5OTkgb3IgX2VuZF8gYXQgdGhhdCBkYXRlLiBGb3IgdGhlIHB1cnBvc2VzIG9mIHRoaXMgZXhlcmNpc2UsIHdlJ2xsIGdvIHdpdGggZW5jb3VudGVycyB0aGF0IF9iZWdpbl8gYWZ0ZXIgdGhhdCBkYXRlIGR1ZSB0byB0aGUgdGVybSBfb2NjdXJfIGluIHRoZSBzcGVjaWZpY2F0aW9uLgoKYGBge3J9CmFmdGVyX2RhdGUgPC0gZmlsdGVyKGRydWdfb3ZlcmRvc2VzLCBTVEFSVCA+ICIxOTk5LTA3LTE1IikKYXJyYW5nZShhZnRlcl9kYXRlLCBTVEFSVCkKYGBgCgpOb3cgd2UncmUgY29uY2VybmVkIHdpdGggZW5jb3VudGVycyB3aXRoIHBhdGllbnRzIGJldHdlZW4gdGhlIGFnZXMgb2YgMTggYW5kIDM1OyB3ZSdsbCBuZWVkIHRvIGpvaW4gdGhlIGBwYXRpZW50c2AgdGFibGUgdG8gaGFuZGxlIHRoYXQuCgpgYGB7cn0Kd2l0aF9wYXRpZW50cyA8LSBpbm5lcl9qb2luKGFmdGVyX2RhdGUsIHBhdGllbnRzLCBjKCJQQVRJRU5UIiA9ICJJZCIpKQp3aXRoX3BhdGllbnRzCmBgYAoKQmFzZWQgdXBvbiB0aGUgd29yZGluZyBpbiB0aGUgc3BlY2lmaWNhdGlvbnMsIHRoZSBwYXRpZW50J3MgYWdlIG11c3QgYmUgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDE4IGF0IHRoZSBzdGFydCBvZiBhbiBlbmNvdW50ZXIgYW5kIGxlc3MgdGhhbiBvciBlcXVhbCB0byAzNSBhdCB0aGUgZW5kIG9mIHRoZSBlbmNvdW50ZXIuCgpMZXQncyBtYWtlIHN1cmUgdGhhdCB0aGVyZSBhcmUgbm8gZW5jb3VudGVycyBpbiBvdXIgdGFibGUgdGhhdCBoYXMgbm90IGVuZGVkLCBiZWNhdXNlIGEgcGF0aWVudCBjb3VsZCBhZ2UgdG8gMzYgYnkgdGhlIHRpbWUgdGhlIGVuY291bnRlciBpcyBvdmVyLgoKYGBge3J9Cm5vdF9lbmRlZCA8LSBkcm9wX25hKHdpdGhfcGF0aWVudHMsIFNUT1ApCm5vdF9lbmRlZApgYGAKClR1cm5zIG91dCB3ZSdyZSBvay4gTGV0J3MgZG8gdGhlIGZpbHRlcmluZyBub3cuCkZpcnN0IHdlJ2xsIG5lZWQgdG8gY2FsY3VsYXRlIHRoZSBhZ2Ugb2YgdGhlIHBhdGllbnQgYXQgdGhlIHN0YXJ0IGFuZCBlbmQgb2YgdGhlIGVuY291bnRlci4KCmBgYHtyfQphZ2UgPC0gbXV0YXRlKG5vdF9lbmRlZCwgQUdFQVRTVEFSVCA9IGFzLnBlcmlvZChpbnRlcnZhbChCSVJUSERBVEUsIFNUQVJUKSkkeWVhcikKYWdlIDwtIG11dGF0ZShhZ2UsIEFHRUFUU1RPUCA9IGFzLnBlcmlvZChpbnRlcnZhbChCSVJUSERBVEUsIFNUT1ApKSR5ZWFyKQpzZWxlY3QoYWdlLCBJZCwgQUdFQVRTVEFSVCwgQUdFQVRTVE9QKQpgYGAKCmBgYHtyfQphZ2VkIDwtIGZpbHRlcihhZ2UsIEFHRUFUU1RBUlQgPj0gMTggJiBBR0VBVFNUT1AgPD0gMzUpCmFnZWQKYGBgCgpUaGF0IGZpbmlzaGVzIHVwIHRoZSBmaXJzdCB0YXNrLgoKLS0tCgojIyMgUGFydCAyOiBDcmVhdGUgYWRkaXRpb25hbCBmaWVsZHMKV2l0aCB5b3VyIGRydWcgb3ZlcmRvc2UgZW5jb3VudGVyLCBjcmVhdGUgdGhlIGZvbGxvd2luZyBpbmRpY2F0b3JzOgoKMS4gYERFQVRIX0FUX1ZJU0lUX0lORGA6IGAxYCBpZiBwYXRpZW50IGRpZWQgZHVyaW5nIHRoZSBkcnVnIG92ZXJkb3NlIGVuY291bnRlciwgYDBgIGlmIHRoZSBwYXRpZW50IGRpZWQgYXQgYSBkaWZmZXJlbnQgdGltZQoyLiBgQ09VTlRfQ1VSUkVOVF9NRURTYDogQ291bnQgb2YgYWN0aXZlIG1lZGljYXRpb25zIGF0IHRoZSBzdGFydCBvZiB0aGUgZHJ1ZyBvdmVyZG9zZSBlbmNvdW50ZXIKMy4gYENVUlJFTlRfT1BJT0lEX0lORGA6IGAxYCBpZiB0aGUgcGF0aWVudCBoYWQgYXQgbGVhc3Qgb25lIGFjdGl2ZSBtZWRpY2F0aW9uIGF0IHRoZSBzdGFydCBvZiB0aGUgb3ZlcmRvc2UgZW5jb3VudGVyIHRoYXQgaXMgb24gdGhlIE9waW9pZHMgTGlzdCAocHJvdmlkZWQgYmVsb3cpLCAwIGlmIG5vdCAKNC4gYFJFQURNSVNTSU9OXzkwX0RBWV9JTkRgOiBgMWAgaWYgdGhlIHZpc2l0IHJlc3VsdGVkIGluIGEgc3Vic2VxdWVudCBkcnVnIG92ZXJkb3NlIHJlYWRtaXNzaW9uIHdpdGhpbiA5MCBkYXlzLCAwIGlmIG5vdCAKNS4gYFJFQURNSVNTSU9OXzMwX0RBWV9JTkRgOiBgMWAgaWYgdGhlIHZpc2l0IHJlc3VsdGVkIGluIGEgc3Vic2VxdWVudCBkcnVnIG92ZXJkb3NlIHJlYWRtaXNzaW9uIHdpdGhpbiAzMCBkYXlzLCAwIGlmIG5vdCBvdmVyZG9zZSBlbmNvdW50ZXIsIGAwYCBpZiBub3QKNi4gYEZJUlNUX1JFQURNSVNTSU9OX0RBVEVgOiBUaGUgZGF0ZSBvZiB0aGUgaW5kZXggdmlzaXQncyBmaXJzdCByZWFkbWlzc2lvbiBmb3IgZHJ1ZyBvdmVyZG9zZS4gRmllbGQgc2hvdWxkIGJlIGxlZnQgYXMgYE4vQWAgaWYgbm8gcmVhZG1pc3Npb24gZm9yIGRydWcgb3ZlcmRvc2Ugd2l0aGluIDkwIGRheXMKCi0tLQoKT3Bpb2lkcyBMaXN0OgoqIEh5ZHJvbW9ycGhvbmUgMzI1TWcKKiBGZW50YW55bCDigJMgMTAwIE1DRwoqIE94eWNvZG9uZS1hY2V0YW1pbm9waGVuIDEwMCBNbAoKLS0tCgpPaywgbG9va2luZyBhdCB0aGUgZGF0YSwgaXQgc2VlbXMgdGhlIG9ubHkgZmllbGQgd2UgaGF2ZSB0byBpbmZlciBkZWF0aCBvbiBpcyBpbiB0aGUgYHBhdGllbnRzYCB0YWJsZSB3aXRoIHRoZSBgREVBVEhEQVRFYCBjb2x1bW4uIElmIHRoZSBkYXRlIGZhbGxzIHdpdGhpbiB0aGUgZW5jb3VudGVyIGRhdGVzLCB0aGVuIHdlJ2xsIG1hcmsgaXQgYDFgLiBUaGUgc3BlY2lmaWNhdGlvbnMgZG9uJ3Qgc3RhdGUgd2hhdCB0byBkbyBpZiB0aGUgcGF0aWVudCBoYXNuJ3QgZGllZCwgdGhpcyB3b3VsZCBuZWVkIGNsYXJpZmljYXRpb24sIGJ1dCBmb3IgdGhlIHB1cnBvc2VzIG9mIHRoaXMgZXhlcmNpc2Ugd2UnbGwgbGVhdmUgaXQgYmxhbmsgaW4gdGhvc2UgY2FzZXMuCgpgYGB7cn0KZGllZCA8LSBtdXRhdGUoYWdlZCwgREVBVEhfQVRfVklTSVRfSU5EID0gYXMuaW50ZWdlcihERUFUSERBVEUgPj0gU1RBUlQgJiBERUFUSERBVEUgPD0gU1RPUCkpCnNlbGVjdChkaWVkLCBTVEFSVCwgU1RPUCwgREVBVEhEQVRFLCBERUFUSF9BVF9WSVNJVF9JTkQpCmBgYAoKRm9yIGBDT1VOVF9DVVJSRU5UX01FRFNgIHdlJ2xsIGhhdmUgdG8gdXNlZCB0aGUgYG1lZGljYXRpb25zYCB0YWJsZS4KCkZvciBgQ1VSUkVOVF9PUE9JRF9JTkRgIHdlJ2xsIGhhdmUgdG8gbG9va3VwIHRoZSBjb2RlcyBmb3IgdGhlIG9wb2lkcyBpbiBxdWVzdGlvbjsgaG93ZXZlciwgdGhlIGNvZGVzIGluIHRoZSBgbWVkaWNhdGlvbnNgIHRhYmxlIGRvIG5vdCBhcHBlYXIgdG8gbWF0Y2ggdXAgd2l0aCByZXN1bHRzIGZvdW5kIG9uOiBodHRwczovL21vci5ubG0ubmloLmdvdi9SeE5hdi8gKHdoaWNoIGlzIHRoZSBSeE5vcm0gZGF0YWJhc2UgdGhlIGRhdGEgZGljdGlvbmFyeSBtZW50aW9uZWQpLiBXZSB3b3VsZCBuZWVkIGNsYXJpZmljYXRpb24gb24gdGhpcywgYnV0IGZvciB0aGlzIGV4ZXJjaXNlIHdlJ2xsIHNlYXJjaCBieSB0aGUgYERFU0NSSVBUSU9OYCBjb2x1bW4gaW5zdGVhZC4KClNvbWUgZHJ1Z3MgaGF2ZSBtdWx0aXBsZSBjb21wb25lbnRzL2luZ3JlZGllbnRzLiBJdCdzIHVuc3VyZSB3aGV0aGVyIHdlIG9ubHkgc2hvdWxkIGNvbmNlcm4gb3Vyc2VsdmVzIHdpdGggdGhlIF9wdXJlXyBkcnVncyBvZiBpbnRlcmVzdCBvciBhbHNvIHRoZXNlLiBGb3IgZXhhbXBsZTogYEFtbG9kaXBpbmUgNSBNRyAvIEZlbnRhbnlsIDEwMCBNQ0cgLyBPbG1lc2FydGFuIG1lZG94b21pbCAyMCBNR2AgdnMgYEZlbnRhbnlsIDEwMCBNQ0dgLiBXZSB3b3VsZCBuZWVkIGNsYXJpZmljYXRpb24sIGJ1dCBmb3IgdGhlIGV4ZXJjaXNlIHdlJ2xsIG9ubHkgZXhhbWluZSB0aGUgX3B1cmVfIGRydWdzIGJlY2F1c2UgbXVsdGlwbGUgaW5ncmVkaWVudHMgY2FuIG1vZHVsYXRlIHRoZSBlZmZlY3RzIG9mIHRoZSBkcnVnIGluIHF1ZXN0aW9uLCB0aGlzIGlzIG9mIGNvdXJzZSBhbiBhc3N1bXB0aW9uOyBhbmQgaXQncyBhIGxpdHRsZSBjbG9zZXIgdG8gdGhlIHNwZWNpZmljYXRpb24uCgpgYGB7cn0Kb3BvaWRzID0gYygiSHlkcm9tb3JwaG9uZSAzMjUgTUciLCAiRmVudGFueWwgMTAwIE1DRyIsICJPeHljb2RvbmUtYWNldGFtaW5vcGhlbiAxMDBNTCIpCgpnZXRfbWVkcyA8LSBmdW5jdGlvbihzdGFydCwgcHQpIHsKICBmaWx0ZXIobWVkaWNhdGlvbnMsIFBBVElFTlQgPT0gcHQgJiBTVEFSVCA8PSBzdGFydCAmIFNUT1AgPD0gc3RhcnQpCn0KCmN1cnJlbnRfbWVkcyA8LSBkaWVkICU+JQogIG11dGF0ZShDVVJSRU5UTUVEUyA9IHBtYXAobGlzdChTVEFSVCwgUEFUSUVOVCksIGdldF9tZWRzKSkgJT4lCiAgbXV0YXRlKENPVU5UX0NVUlJFTlRfTUVEUyA9IG1hcF9pbnQoQ1VSUkVOVE1FRFMsIG5yb3cpKSAlPiUKICBtdXRhdGUoQ1VSUkVOVF9PUE9JRF9JTkQgPSBtYXBfaW50KENVUlJFTlRNRURTLCBmdW5jdGlvbihtZWQpIGFueShtZWQkREVTQ1JJUFRJT04gJWluJSBvcG9pZHMpKSkKICAgICAgICAgCnNlbGVjdChjdXJyZW50X21lZHMsIENVUlJFTlRNRURTLCBDT1VOVF9DVVJSRU5UX01FRFMsIENVUlJFTlRfT1BPSURfSU5EKQpgYGAKCk5vdyB3ZSBjYW4gYmVnaW4gbG9va2luZyBhdCByZWFkbWlzc2lvbnMuIExldCdzIGNhbGN1bGF0ZSBob3cgbWFueSBkYXlzIGl0IHdpbGwgYmUgdG8gdGhlIG5leHQgZW5jb3VudGVyLgoKYGBge3J9CnJlYWRtaXNzaW9uIDwtIGN1cnJlbnRfbWVkcyAlPiUKICBhcnJhbmdlKFNUQVJUKSAlPiUKICBncm91cF9ieShQQVRJRU5UKSAlPiUKICBtdXRhdGUoUkVBRE1JU1NJT04gPSBhcy5wZXJpb2QobGVhZChTVEFSVCkgLSBTVE9QKSRkYXkpICU+JQogIG11dGF0ZShGSVJTVF9SRUFETUlTU0lPTl9EQVRFID0gbGVhZChTVEFSVCkpICU+JQogIHVuZ3JvdXAKCnJlYWRtaXNzaW9uJFJFQURNSVNTSU9OCmBgYAoKR3JlYXQsIG5vdyB3ZSBjYW4gYWRkIHRoZSBgUkVBRE1JU1NJT05fOTBfREFZX0lORGAgYW5kIGBSRUFETUlTU0lPTl8zMF9EQVlfSU5EYCBjb2x1bW5zLgoKYGBge3J9CmZpbHRlcmVkX3JlYWRtaXNzaW9uIDwtIHJlYWRtaXNzaW9uICU+JQogIG11dGF0ZShSRUFETUlTU0lPTl85MF9EQVlfSU5EID0gYXMuaW50ZWdlcihSRUFETUlTU0lPTiA8PSA5MCkpICU+JQogIG11dGF0ZShSRUFETUlTU0lPTl8zMF9EQVlfSU5EID0gYXMuaW50ZWdlcihSRUFETUlTU0lPTiA8PSAzMCkpCgphcnJhbmdlKHNlbGVjdChmaWx0ZXJlZF9yZWFkbWlzc2lvbiwgUkVBRE1JU1NJT04sIFJFQURNSVNTSU9OXzkwX0RBWV9JTkQsIFJFQURNSVNTSU9OXzMwX0RBWV9JTkQpLCBSRUFETUlTU0lPTikKYGBgCgpFYXJsaWVyIHdoZW4gZmlndXJpbmcgb3V0IGhvdyBtYW55IGRheXMgdGhlcmUgd2FzIHRpbGwgYSByZWFkbWlzc2lvbiwgd2UgYWxzbyBwdXQgaW4gd2hhdCB0aGUgcmVhZG1pc3Npb24gZGF0ZSBpcy4gV2UgY2FuIHVzZSB0aGF0IGhlcmUuIFRoaXMgd29ya3MgYmVjYXVzZSBiZWZvcmUgZG9pbmcgdGhhdCwgd2Ugc29ydGVkIHRoZSBncm91cCBkYXRhZnJhbWVzIGJ5IGBTVEFSVGAuIFRoaXMgd2F5IHRoZSBuZXh0IGRhdGUgaXMgZ3VhcmFudGVlZCB0byBiZSB0aGUgZmlyc3QgcmVhZG1pc3Npb24gYWZ0ZXIgYSBzcGVjaWZpYyBlbmNvdW50ZXIuCgpgYGB7cn0KI2ZpcnN0X3JlYWRtaXNzaW9uX2RhdGVkIDwtIG11dGF0ZShmaWx0ZXJlZF9yZWFkbWlzc2lvbiwgRklSU1RfUkVBRE1JU1NJT05fREFURSA9IGFzX2RhdGV0aW1lKGlmZWxzZShSRUFETUlTU0lPTl85MF9EQVlfSU5EID09IDEsIFJFQURNSVNTSU9OREFURSwgTkEpKSkKZmlyc3RfcmVhZG1pc3Npb25fZGF0ZWQgPC0gbXV0YXRlKGZpbHRlcmVkX3JlYWRtaXNzaW9uLCBGSVJTVF9SRUFETUlTU0lPTl9EQVRFID0gcmVwbGFjZShGSVJTVF9SRUFETUlTU0lPTl9EQVRFLCBSRUFETUlTU0lPTl85MF9EQVlfSU5EID09IDAsIE5BKSkKCmFycmFuZ2Uoc2VsZWN0KGZpcnN0X3JlYWRtaXNzaW9uX2RhdGVkLCBSRUFETUlTU0lPTiwgUkVBRE1JU1NJT05fOTBfREFZX0lORCwgUkVBRE1JU1NJT05fMzBfREFZX0lORCwgRklSU1RfUkVBRE1JU1NJT05fREFURSksIFJFQURNSVNTSU9OKQpgYGAKCkFuZCBub3cgd2UgY2FuIG1vdmUgb250byB0aGUgZmluYWwgdGFzay4KCi0tLQoKIyMjIFBhcnQgMzogRXhwb3J0IHRoZSBkYXRhIHRvIGEgYENTVmAgZmlsZQoKRXhwb3J0IGEgZGF0YXNldCBjb250YWluaW5nIHRoZXNlIHJlcXVpcmVkIGZpZWxkczoKCnwgRmllbGQgbmFtZSAgICAgICAgICAgICAgICB8IEZpZWxkIERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBEYXRhIFR5cGUgICAgICAgIHwKfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0gfAp8IGBQQVRJRU5UX0lEYCAgICAgICAgICAgICAgfCBQYXRpZW50IGlkZW50aWZpZXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgQ2hhcmFjdGVyIFN0cmluZyB8CnwgYEVOQ09VTlRFUl9JRGAgICAgICAgICAgICB8IFZpc2l0IGlkZW50aWZpZXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBDaGFyYWN0ZXIgc3RyaW5nIHwKfCBgSE9TUElUQUxfRU5DT1VOVEVSX0RBVEVgIHwgQmVnaW5uaW5nIG9mIGhvc3BpdGFsIGVuY291bnRlciBkYXRlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IERhdGUvdGltZSAgICAgICAgfAp8IGBBR0VfQVRfVklTSVRgICAgICAgICAgICAgfCBQYXRpZW50IGFnZSBhdCBhZG1pc3Npb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtICAgICAgICAgICAgICB8CnwgYERFQVRIX0FUX1ZJU0lUX0lORGAgICAgICB8IEluZGljYXRvciBpZiB0aGUgcGF0aWVudCBkaWVkIGR1cmluZyB0aGUgZHJ1ZyBvdmVyZG9zZSBlbmNvdW50ZXIuIExlYXZlIGBOL0FgIGlmIHBhdGllbnQgaGFzIG5vdCBkaWVkLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAwIC8xICAgICAgICAgICAgIHwKfCBgQ09VTlRfQ1VSUkVOVF9NRURTYCAgICAgIHwgQ291bnQgb2YgYWN0aXZlIG1lZGljYXRpb25zIGF0IHRoZSBzdGFydCBvZiB0aGUgZHJ1ZyBvdmVyZG9zZSBlbmNvdW50ZXIgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtICAgICAgICAgICAgICB8CnwgYENVUlJFTlRfT1BJT0lEX0lORGAgICAgICB8IGlmIHRoZSBwYXRpZW50IGhhZCBhdCBsZWFzdCBvbmUgYWN0aXZlIG1lZGljYXRpb24gYXQgdGhlIHN0YXJ0IG9mIHRoZSBvdmVyZG9zZSBlbmNvdW50ZXIgdGhhdCBpcyBvbiB0aGUgT3Bpb2lkcyBMaXN0IChwcm92aWRlZCBiZWxvdykgICAgIHwgMC8xICAgICAgICAgICAgICB8CnwgYFJFQURNSVNTSU9OXzkwX0RBWV9JTkRgICB8IEluZGljYXRvciBpZiB0aGUgdmlzaXQgcmVzdWx0ZWQgaW4gYSBzdWJzZXF1ZW50IHJlYWRtaXNzaW9uIHdpdGhpbiA5MCBkYXlzICAgICB8IDAvMSAgICAgICAgICAgICAgfAp8IGBSRUFETUlTU0lPTl8zMF9EQVlfSU5EYCAgfCBJbmRpY2F0b3IgaWYgdGhlIHZpc2l0IHJlc3VsdGVkIGluIGEgc3Vic2VxdWVudCByZWFkbWlzc2lvbiB3aXRoaW4gMzAgZGF5cyAgICAgfCAwLzEgICAgICAgICAgICAgIHwKfCBgRklSU1RfUkVBRE1JU1NJT05fREFURWAgIHwgRGF0ZSBvZiB0aGUgZmlyc3QgcmVhZG1pc3Npb24gZm9yIGRydWcgb3ZlcmRvc2Ugd2l0aGluIDkwIGRheXMuIExlYXZlIGBOL0FgIGlmIG5vIHJlYWRtaXNzaW9ucyBmb3IgZHJ1ZyBvdmVyZG9zZSB3aXRoaW4gOTAgZGF5cy4gfCBEYXRlL3RpbWUgICAgICAgIHwKCi0tLQoKVGhpcyBhbnN3ZXJzIG91ciBxdWVzdGlvbiBhYm91dCBgREVBVEhfQVRfVklTSVRfSU5EYCBlYXJsaWVyLiBPdXIgYXNzdW1wdGlvbiB3YXMgY29ycmVjdCB0byBsZWF2ZSBpdCBhcyBgTi9BYCBpZiB0aGUgcGF0aWVudCBoYXMgbm90IGRpZWQuCgpOb3cgd2UganVzdCBuZWVkIHRvIGdyYWIgdGhlc2UgZmllbGRzIGFuZCB3cml0ZSBpdCB0byBhIGBDU1ZgLgoKYGBge3J9Cm91dHB1dCA8LSBmaXJzdF9yZWFkbWlzc2lvbl9kYXRlZCAlPiUKICBzZWxlY3QoUEFUSUVOVCwgSWQsIFNUQVJULCBBR0VBVFNUQVJULCBERUFUSF9BVF9WSVNJVF9JTkQsIENPVU5UX0NVUlJFTlRfTUVEUywgQ1VSUkVOVF9PUE9JRF9JTkQsIFJFQURNSVNTSU9OXzkwX0RBWV9JTkQsIFJFQURNSVNTSU9OXzMwX0RBWV9JTkQsIEZJUlNUX1JFQURNSVNTSU9OX0RBVEUpICU+JQogIHJlbmFtZShQQVRJRU5UX0lEID0gUEFUSUVOVCwgRU5DT1VOVEVSX0lEID0gSWQsIEhPU1BJVEFMX0VOQ09VTlRFUl9EQVRFID0gU1RBUlQsIEFHRV9BVF9WSVNJVCA9IEFHRUFUU1RBUlQpCiAgCm91dHB1dApgYGAKCmBgYHtyfQpyZWFkcjo6d3JpdGVfY3N2KG91dHB1dCwgInN1Ym1pc3Npb24uY3N2IiwgbmEgPSAiTi9BIiwgYXBwZW5kID0gRkFMU0UpCmBgYA==